﻿namespace TestApp.DataService
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Linq;
    using System.Reactive.Concurrency;
    using System.Reactive.Disposables;
    using System.Reactive.Linq;
    using Contracts;
    using JetBrains.Annotations;

    internal sealed class DataSender : IDataSender
    {
        private readonly IScheduler _scheduler;
        private readonly IDataAggregator _dataAggregator;
        private readonly CompositeDisposable _anchors = new CompositeDisposable();
        private IDataServiceCallback _dataServiceCallback;
        private int? _index;
        private long _curVersion;

        public DataSender(
            [NotNull] IScheduler scheduler,
            [NotNull] IDataAggregator dataAggregator)
        {
            Contract.Requires<ArgumentNullException>(scheduler != null);
            Contract.Requires<ArgumentNullException>(dataAggregator != null);
            _scheduler = scheduler;
            _dataAggregator = dataAggregator;
        }

        public void Initialize(IDisposable anchors, IDataServiceCallback dataServiceCallback, int? index = null)
        {
            Contract.Requires<ArgumentNullException>(anchors != null);
            Contract.Requires<ArgumentNullException>(dataServiceCallback != null);
            _dataServiceCallback = dataServiceCallback;
            _index = index;

            var entitiesSrc =
                from hasNewEntity in _dataAggregator.CreateHasNewEntitySource()
                let entities = GetEntities(ref _curVersion)
                where entities.Count > 0
                select entities;

            _anchors.Add(anchors);
            _anchors.Add(entitiesSrc.ObserveOn(_scheduler).Subscribe(ProcessEntities));
        }

        public void Check()
        {
            _scheduler.Schedule(PingInternal);
        }

        public void Dispose()
        {
            _anchors.Dispose();
        }

        private void PingInternal()
        {
            try
            {
                _dataServiceCallback.Ping();
            }
            catch(Exception)
            {
                MarkAsFailed();
            }
        }

        private void ProcessEntities(IList<EntityDto> entities)
        {
            try
            {
                _dataServiceCallback.ProcessEntities(entities);
            }
            catch (Exception)
            {
                MarkAsFailed();
            }
        }

        private void MarkAsFailed()
        {
            _anchors.Dispose();
        }

        [NotNull] private IList<EntityDto> GetEntities(ref long curVersion)
        {
            Contract.Ensures(Contract.Result<IEnumerable<EntityDto>>() != null);
            IEnumerable<EntityDto> entities;
            if (_dataAggregator.TryGetEntities(curVersion, out curVersion, out entities))
            {
                if (_index == null)
                {
                    return entities.ToArray();
                }

                return entities.Where(entity => FilterByIndex(entity, _index.Value)).ToArray();
            }

            return Enumerable.Empty<EntityDto>().ToArray();
        }

        private static bool FilterByIndex([NotNull] EntityDto entity, int index)
        {
            Contract.Requires<ArgumentNullException>(entity != null);
            var dataEntity = entity as DataEntityDto;
            return dataEntity == null || dataEntity.Index == index;
        }
    }
}
